import {
    AudioSource,
    resources,
    SpriteFrame,
    UIOpacity,
    Sprite,
    BlockInputEvents,
    Label,
    Widget,
    Component,
    director,
    Node,
    sys, Color
} from "cc";
import {GlobalConst} from "db://assets/scripts/consts/GlobalConst";
import {SocketMessage} from "db://assets/scripts/dto/SocketMessage";
import {SocketLogicInterface} from "db://assets/scripts/interface/SocketLogicInterface";

export class WebsocketMgr {

    private static _inst: WebsocketMgr;
    public static get inst(): WebsocketMgr {
        if (this._inst == null) {
            this._inst = new WebsocketMgr();
        }
        return this._inst;
    }

    public socketLogic: Map<string, SocketLogicInterface> = new Map();
    // 离线通知消息
    public officeMessage: Map<Component, () => void> = new Map();
    // 对手离线通知消息
    public opponentOfficeMessage: Map<Component, () => void> = new Map();
    // 重连成功消息
    public reconnectSuccessMessage: Map<Component, () => void> = new Map();
    // 对手重连成功消息
    public opponentReconnectSuccessMessage: Map<Component, () => void> = new Map();

    /**
     * 重连失败消息
     */
    public reconnectFailMessage: Map<Component, () => void> = new Map();


    private socket: WebSocket;


    // 是否在线 0:初始化 -1:离线 1:在线
    isOnline: number = 0;

    constructor() {
        //@en create a node as audioMgr
        //@zh 创建一个节点作为 audioMgr
        let webSocktMgr = new Node();
        webSocktMgr.name = '__websocketMgr__';

        //@en add to the scene.
        //@zh 添加节点到场景
        director.getScene().addChild(webSocktMgr);
        //@en make it as a persistent node, so it won't be destroied when scene change.
        //@zh 标记为常驻节点，这样场景切换的时候就不会被销毁了
        director.addPersistRootNode(webSocktMgr);
    }

    public initSocket() {

        console.log("开始时重连", !this.socket, this.isOnline)
        if (!this.socket || this.isOnline !== 1) {
            let url = GlobalConst.webSocketApi + sys.localStorage.getItem('token');
            console.log("websocket建立连接:" + url);
            this.socket = new WebSocket(url);


            // 连接成功
            this.socket.onopen = this.onSocketOpen;
            //连接错误
            this.socket.onerror = this.onSocketError;
            //接收信息
            this.socket.onmessage = this.onSocketMessage;
            //连接关闭
            this.socket.onclose = this.onSocketClose;

            // 开启心跳
            this.startHeartbeat();
        }

    }


    private onSocketOpen(event: Event) {
        console.log("建立连接成功", event);
        WebsocketMgr.inst.closeReconnectLabel();

        if (WebsocketMgr.inst.isOnline === -1) {
            // 发送重新连接成功消息
            WebsocketMgr.inst.reconnectAttempts = 0;
            WebsocketMgr.inst.noticeReconnectSuccess();

        }
        WebsocketMgr.inst.isOnline = 1;
    }

    private onSocketError(event: Event) {
        console.log("连接错误", event);
        WebsocketMgr.inst.isOnline = -1;
    }

    private onSocketMessage(event: MessageEvent) {
        if (event.data === "read") {
            console.log("socket收到消息", 'read');
            WebsocketMgr.inst.resetHeartbeat();
            // 返回的是已读 不处理
            return;
        }

        console.log("socket收到消息", event.data);

        //消息处理
        let socketMessage: SocketMessage = JSON.parse(event.data);
        let events = socketMessage.event.split("/");
        if (events[0] === 'opponentOffice') {
            for (const [key, value] of WebsocketMgr.inst.opponentOfficeMessage) {
                value.apply(key);
            }
            return;
        }
        if (events[0] === 'opponentReconnect') {
            console.log("对手重连")
            for (const [key, value] of WebsocketMgr.inst.opponentReconnectSuccessMessage) {
                value.apply(key);
            }
            return;
        }


        let socketLogicInterface = WebsocketMgr.inst.socketLogic.get(events[0]);
        if (socketLogicInterface == null) {
            if (socketMessage.event !== "home/matchSuccess") {
                console.error("未找到消息处理类型" + socketMessage.event)
            }
            return;
        }

        socketLogicInterface.accept(socketMessage);
    }

    private onSocketClose(event: CloseEvent) {
        console.log("连接关闭", event);
        WebsocketMgr.inst.heartbeatTimeout && clearTimeout(WebsocketMgr.inst.heartbeatTimeout);
        WebsocketMgr.inst.heartbeatServerTimeout && clearTimeout(WebsocketMgr.inst.heartbeatServerTimeout);
        WebsocketMgr.inst.noticeOffice();
        WebsocketMgr.inst.reconnect();
    }

    public sendMessage(message: SocketMessage) {
        if (WebsocketMgr.inst.isOnline === 1) {
            this.socket.send(JSON.stringify(message))
        } else {
            setTimeout(() => {
                this.sendMessage(message);
            }, 1)
        }
    }


    /**
     * 超时时间
     */
    heartbeatTimeout: number;
    heartbeatServerTimeout: number;
    heartbeatTime: number = 20 * 1000;

    private startHeartbeat() {

        this.heartbeatTimeout && clearTimeout(this.heartbeatTimeout);
        this.heartbeatServerTimeout && clearTimeout(this.heartbeatServerTimeout);

        this.heartbeatTimeout = setTimeout(() => {
            if (this.socket.readyState === 1) {
                console.log("发送心跳")
                this.socket.send("ping");
            } else {
                console.log("不在连接,发送通知")
                this.noticeOffice();
                this.reconnect();
            }

            // 长时间没清除就代表服务器没响应,关闭连接
            this.heartbeatServerTimeout = setTimeout(() => {
                console.log("ping超时,连接关闭")
                //超时关闭
                this.socket.close();
                this.noticeOffice();
                this.reconnect();
            }, this.heartbeatTime);

        }, this.heartbeatTime);
    }

    noticeReconnectSuccess() {

        for (const [key, value] of this.reconnectSuccessMessage) {
            value.apply(key);
        }
    }

    noticeOffice() {
        this.isOnline = -1;

        for (const [key, value] of this.officeMessage) {
            value.apply(key);
        }
    }

    resetHeartbeat() {
        //重置心跳
        //清除时间
        clearTimeout(this.heartbeatTimeout);
        clearTimeout(this.heartbeatServerTimeout);
        //重启心跳
        this.startHeartbeat();
    }

    // 是否正在连接
    lockReconnect: boolean = false;
    lockReconnectTimeout: number;
    // 间隔时间
    baseInterval: number = 15000;
    // 最大连接次数
    maxReconnectAttempts: number = 6;
    // 当前连接次数
    reconnectAttempts: number = 0;

    /**
     * 重新连接
     * @private
     */
    public reconnect() {
        if (this.lockReconnect) {
            return
        }
        this.createLabelReconnect();
        if (this.reconnectAttempts >= this.maxReconnectAttempts) {
            console.error('重连次数已达上限');
            this.updateReconnectLabel('重连次数已达上限,请检查网络!');
            for (const [key, value] of WebsocketMgr.inst.reconnectFailMessage) {
                value.apply(key);
            }
            return;
        }
        console.log("开始重新连接")
        let intervalTimeOut = this.baseInterval;
        if (this.reconnectAttempts == 0) {
            intervalTimeOut = 0;
        } else if (this.reconnectAttempts == 1) {
            intervalTimeOut = 1000;
        }
        // 正在连接
        this.lockReconnect = true;
        this.lockReconnectTimeout && clearTimeout(this.lockReconnectTimeout);
        this.lockReconnectTimeout = setTimeout(() => {
            this.reconnectAttempts++;
            console.log(`第${this.reconnectAttempts}次重连...`);
            //初始化新连接
            this.initSocket();
            //把链接状态改为false
            this.lockReconnect = false;
        }, intervalTimeOut);
    }

    timeOutReconnectLabel: number;
    reconnectLabel: Label;
    marskLabelNode: Node;

    public createLabelReconnect() {
        if (this.reconnectLabel && this.reconnectLabel.string === "重连次数已达上限,请检查网络!") {
            this.reconnectLabel.string = '重连中';
            this.timeOutReconnectLabel = setInterval(() => {
                this.reconnectLabel.string = '重连中' + '.';
                if (this.reconnectLabel.string === '重连中...') {
                    this.reconnectLabel.string = '重连中';
                }
            }, 1000)
            return;
        }
        if (this.reconnectLabel) {
            return;
        }

        this.marskLabelNode = new Node('MarskReconnectMarsk');
        this.marskLabelNode.parent = director.getScene().getChildByName("Canvas");


        let sprite: Sprite = this.marskLabelNode.addComponent(Sprite);
        resources.load("imgs/whiteBg/spriteFrame", SpriteFrame, (err, spriteFrame) => {
            sprite.spriteFrame = spriteFrame;
            const widget = this.marskLabelNode.addComponent(Widget);
            widget.isAlignLeft = widget.isAlignRight = widget.isAlignTop = widget.isAlignBottom = true;
            widget.left = widget.right = widget.top = widget.bottom = 0;

            // 添加背景颜色
            const color = this.marskLabelNode.addComponent(UIOpacity);
            color.opacity = 150; // 半透明效果
            this.marskLabelNode.addComponent(BlockInputEvents); // 阻止下层交互

        });

        const labelNode = new Node('MyReconnectLabel');
        labelNode.parent = director.getScene().getChildByName("Canvas");

        this.reconnectLabel = labelNode.addComponent(Label);

        this.reconnectLabel.fontSize = 50;
        this.reconnectLabel.lineHeight = 60;
        this.reconnectLabel.isBold = true;
        this.reconnectLabel.color = new Color(0, 0, 0, 255);
        this.reconnectLabel.string = '重连中';
        this.timeOutReconnectLabel = setInterval(() => {
            console.log("定时器")
            if (this.reconnectLabel.string === '重连中...') {
                console.log("定时器2")
                this.reconnectLabel.string = '重连中';
            }
            this.reconnectLabel.string = this.reconnectLabel.string + '.';
        }, 1000)
    }

    private updateReconnectLabel(str: string) {
        console.log("定时器")

        clearInterval(this.timeOutReconnectLabel);
        this.reconnectLabel.string = str;
    }

    public closeReconnectLabel() {
        this.timeOutReconnectLabel && clearInterval(this.timeOutReconnectLabel);
        if (this.reconnectLabel && this.reconnectLabel.node) {
            this.reconnectLabel.node.destroy();
            this.reconnectLabel=null;
        }
        this.marskLabelNode && this.marskLabelNode.destroy();
        this.marskLabelNode=null;
    }


}